<?php
/**
 * Created by PhpStorm.
 * User: lifeng
 * Date: 16/3/15
 * Time: 下午2:03
 * ===============================
 *          posix标准
 * ===============================
 *      1字节     uint8_t
 *      2字节     uint16_t
 *      4字节     uint32_t
 *      8字节     uint64_t
 * ===============================
 *
 */
class Package{

    private static $_instance = null;
    private $_runTime =false;
    public $typeSize = array(
        't1' =>0,
        't24'=>0,
        't30' => 0,
        't33' => 0,
        't74' => 0,
        't168'=> 0,
    );

    private $_config = array(
        //电池
        '1' => array(
            'onboard_control_sensors_enabled' => 'I',
            'onboard_control_sensors_health'  => 'I',
            'voltage_battery'=>'S',
            'current_battery' =>'s',
            'battery_remaining_mah'=>'S',
            'battery_ temperature'=>'s',
            'failsafe_status'=>'C',
            'extended_status'=>'C',
            'battery_remaining' => 'c',
        ),
        //GPS
        '24' => array(
            'eph' => 'S',
            'epv' => 'S',
            'cog' => 'S',
            'fix_type' => 'C',
            'satellites' => 'C',
        ),
        //气压,机身表面问题
        '29' => array(
            'time_boot_ms' => 'I',
            'press_abs' => 'f',
            'press_diff' => 'f',
            'temperature' => 's'
        ),
        //Steam extra1 角度表盘
        '30' => array(
            'time_boot_ms' => 'I',
            'roll' => 'f',  //滚动角
            'pitch'=> 'f',  //俯仰角
            'yaw'  => 'f',  //偏航角
        ),
        //Stream Postion 位置
        '33' => array(
            'time_boot_ms'=>'I', //时间戳
            'lat'=>'i',          //纬度       *E7
            'lon'=>'i',          //经度   *E7
            'alt'=>'i',          //海拔高度    毫米
            'relative_alt' => 'i',//地面相对高度 毫米
            'vx'=>'s',      //X地速 cm/s
            'vy'=>'s',      //Y地速 cm/s
            'vz'=>'s',      //Z地速 cm/s
        ),
        /**
         * 返航
         * 1001 返航（包括执行任务后的返航、点击返航后的返航、以及点击了中途伞降飞往中途点的返航）
         * 1002 到达降落点上方并等待降落指令（包括执行完任务到达Home点、点击返航到达Home点、中途伞降到达指定点） x
         * 1002 降落时，从任务高度降到盘旋高度（目前滑降是20米，伞降是40米）之间的状态。x
         * 1005 解锁后，抛飞前；以及飞机起飞后，到达盘旋高度50米之前的状态（必须要有Home点、航点信息）x
         * 1006 飞机从Home点上方盘旋高度50米开始盘旋爬高，到进入航线前
         * 1007 滑降时，从盘旋圆出来后，到拉飘前
         * 1007 伞降时，从盘旋圆出来后，到落地
         * 1008  滑降时，拉飘后
         */
        '42' => array(
            'seq' =>'S' //1001
        ),
        '74'=>array(
            'airspeed'      => 'f', //空速 m/s
            'groundspeed'   => 'f', //地速 m/s
            'alt'           => 'f', //海拔高度 m
            'climb'         => 'f', //爬升率 m/s
            'target_airspeed'=>'f', //目标空速 m/s
            'throttle'      => 'S',  //油门 百分比 0~100
            'rpm'           => 'S', //转速
            'current_mileage'=> 'f' //当前里程
        ),
        '180' => array(
            'time_usec' => 'L',
            'lat'       => 'I',
            'lng'       => 'I',
            'alt_ms1'   => 'f',
            'alt_re1'   => 'f',
            'roll'      => 'f',
            'pitch'     => 'f',
            'yaw'       => 'f',
            'foc_len'   => 'f',
            'img_idx'   => 's',
            'target_system' => 'I',
            'cam_idx'   => 'I',
            'flags'     => 'I'
        ),
        '168' => array(
            'direction' => 'f', //风向  单位degrees
            'speed'     => 'f', //水平风速 单位 m/s
            'speed_z'   => 'f', //垂直风速
        ),
        '225' => array(
            'mission_id' => 'a' //飞行模式
        ),
    );
    
    private $_D200Config = array(
        //电池
        '1' => array(
            'calibration_status' => 'C',    //校准的状态
            'calibration'  => 'S',  //校准模块信息
            'code_num'=>'C',    //状态码分组
            'Code0' =>'I',  //状态码
            'Code1'=>'I',   //状态码
            'extended_status'=>'C', //位编码
            'voyage_remaining'=>'S',    //剩余航程
            'distance'=>'f',    //距离单位
        ),
        //GPS
        '24' => array(
            'PDOP' => 'S',  //Ublox
            'fix_type' => 'C',  //高4位为RTK状态低4位为GPS定位状态
            'satellites' => 'C',    //可见卫星数
        ),
        //Steam extra1 角度表盘
        '30' => array(
            'time_boot_ms' => 'I',
            'roll' => 'f',  //滚动角
            'pitch'=> 'f',  //俯仰角
            'yaw'  => 'f',  //偏航角
        ),
        //Stream Postion 位置
        '33' => array(
            'time_boot_ms'=>'I', //时间戳
            'lat'=>'i',          //纬度       *E7
            'lon'=>'i',          //经度   *E7
            'alt'=>'i',          //海拔高度    毫米
            'relative_alt' => 'i',//地面相对高度 毫米
            'vx'=>'s',      //X地速 cm/s
            'vy'=>'s',      //Y地速 cm/s
            'vz'=>'s',      //Z地速 cm/s
        ),
        /**
         * 返航
         * 1001 返航（包括执行任务后的返航、点击返航后的返航、以及点击了中途伞降飞往中途点的返航）
         * 1002 到达降落点上方并等待降落指令（包括执行完任务到达Home点、点击返航到达Home点、中途伞降到达指定点） x
         * 1002 降落时，从任务高度降到盘旋高度（目前滑降是20米，伞降是40米）之间的状态。x
         * 1005 解锁后，抛飞前；以及飞机起飞后，到达盘旋高度50米之前的状态（必须要有Home点、航点信息）x
         * 1006 飞机从Home点上方盘旋高度50米开始盘旋爬高，到进入航线前
         * 1007 滑降时，从盘旋圆出来后，到拉飘前
         * 1007 伞降时，从盘旋圆出来后，到落地
         * 1008  滑降时，拉飘后
         */
        '42' => array(
            'seq' =>'S' //1001
        ),
        '74'=>array(
            'airspeed'      => 'f', //空速 m/s
            'groundspeed'   => 'f', //地速 m/s
            'alt'           => 'f', //海拔高度 m
            'climb'         => 'f', //爬升率 m/s
            'target_airspeed'=>'f', //目标空速 m/s
            'throttle'      => 'S',  //油门 百分比 0~100
            'rpm'           => 'S', //转速
            'current_mileage'=> 'f' //当前里程
        ),
        //Stream Postion 位置
        '55' => array(
            'severe_low_battery_alarm_set'=>'C',    //严重低电量报警
            'low_battery_alarm_set'=>'C',          //低电量报警
            'starting_self_discharge_time'=>'C',    //开始自放电时间
        ),
        //Stream Postion 位置
        '56' => array(
            'battery0_voltage'=>'S',            //电池总电压mv
            'battery0_current'=>'I',            //电池当前电流 mA
            'battery0_temperature'=>'s',        //温度摄氏度*100
            'battery0_remaining'=>'c',          //剩余电量百分比
            'battery1_voltage' => 'S',          //电池总电压mv
            'battery1_current'=>'I',            //电池当前电流 mA
            'battery1_temperature'=>'s',        //温度摄氏度*100
            'battery1_remaining'=>'c',          //剩余电量百分比
            'battery2_voltage' => 'S',          //电池总电压mv
            'battery2_current'=>'I',            //电池当前电流 mA
            'battery2_temperature'=>'s',        //温度摄氏度*100
            'battery2_remaining'=>'c',          //剩余电量百分比
        ),
        '58' => array(
            'battery0_cellVoltage1'=>'S',       //第一节电压mv
            'battery0_cellVoltage2'=>'S',       //第二节电压mv
            'battery0_cellVoltage3'=>'S',       //第三节电压mv
            'battery0_cellVoltage4'=>'S',       //第四节电压mv
            'battery0_cellVoltage5'=>'S',       //第五节电压mv
            'battery0_cellVoltage6'=>'S',       //第六节电压mv
            'battery0_cellVoltage7'=>'S',       //第七节电压mv
            'battery0_cellVoltage8'=>'S',       //第八节电压mv
            'battery1_cellVoltage1'=>'S',       //第一节电压mv
            'battery1_cellVoltage2'=>'S',       //第二节电压mv
            'battery1_cellVoltage3'=>'S',       //第三节电压mv
            'battery1_cellVoltage4'=>'S',       //第四节电压mv
            'battery1_cellVoltage5'=>'S',       //第五节电压mv
            'battery1_cellVoltage6'=>'S',       //第六节电压mv
            'battery1_cellVoltage7'=>'S',       //第七节电压mv
            'battery1_cellVoltage8'=>'S',       //第八节电压mv
            'battery2_cellVoltage1'=>'S',       //第一节电压mv
            'battery2_cellVoltage2'=>'S',       //第二节电压mv
            'battery2_cellVoltage3'=>'S',       //第三节电压mv
            'battery2_cellVoltage4'=>'S',       //第四节电压mv
            'battery2_cellVoltage5'=>'S',       //第五节电压mv
            'battery2_cellVoltage6'=>'S',       //第六节电压mv
            'battery2_cellVoltage7'=>'S',       //第七节电压mv
            'battery2_cellVoltage8'=>'S',       //第八节电压mv
        ),
        '60' => array(
            'battery0_fullCapacity'=>'S',       //充满电量maH
            'battery0_lifePercent'=>'C',        //寿命百分比
            'battery0_cycleTime'=>'S',          //循环次数
            'battery0_batteryId'=>'S',          //电池序列号
            'battery1_fullCapacity' => 'S',     //充满电量maH
            'battery1_lifePercent'=>'C',        //寿命百分比
            'battery1_cycleTime'=>'S',          //循环次数
            'battery1_batteryId'=>'S',          //电池序列号
            'battery2_fullCapacity' => 'S',     //充满电量maH
            'battery2_lifePercent'=>'C',        //寿命百分比
            'battery2_cycleTime'=>'S',          //循环次数
            'battery2_batteryId'=>'S',          //电池序列号
            'severe_low_battery_alarm_set'=>'C',    //严重低电量报警（百分比）
            'low_battery_alarm_set'=>'C',       //低电量报警（百分比）
            'starting_self_discharge_time'=>'C',    //开始自放电时间（天）
        ),
        '180' => array(
            'time_usec' => 'L',
            'lat'       => 'I',
            'lng'       => 'I',
            'alt_msl'   => 'f',
            'alt_rel'   => 'f',
            'roll'      => 'f',
            'pitch'     => 'f',
            'yaw'       => 'f',
            'foc_len'   => 'f',
            'img_idx'   => 's',
            'target_system' => 'C',
            'cam_idx'   => 'C',
            'flags'     => 'C'
        ),
        '168' => array(
            'direction' => 'f', //风向  单位degrees
            'speed'     => 'f', //水平风速 单位 m/s
            'speed_z'   => 'f', //垂直风速
        ),
        '225' => array(
            'mission_id' => 'a' //飞行模式
        ),
    );

    private $_bitSize = array(
        'L' => 8,
        'I' => 4,
        'i' => 4,
        's' => 2,
        'S' => 2,
        'f' => 4,
        'c'=>1,
        'C'=>1,
        'a'=>32,
    );

    public static function getInstance(){
        if(!self::$_instance instanceof self)
            self::$_instance = new self;

        return self::$_instance;
    }

    public function getProtocol($msgid,$payload){
        if (!isset($this->_config[$msgid]))
            return false;

//        $this->typeSize['t'.$msgid] ++;

        $rememberIndex = 0;
        $protocal = array();
        foreach($this->_config[$msgid] as $key =>$type){
            $str = substr($payload,$rememberIndex,$this->_bitSize[$type]*2);

            if(mb_strlen($str)<$this->_bitSize[$type]*2)
                continue;

            if($msgid == '225'){
                if(!($unpackInfo = $this->unpack($str,$type.'*',$key)))
                    continue;
            }else{
                if(!($unpackInfo = $this->unpack($str,$type,$key)))
                    continue;
            }

            if($msgid == '33' &&($key == 'lat' || $key =='lon'))
                $unpackInfo[$key] = $unpackInfo[$key]/pow(10,7);
                
            if($msgid == '24' && $key == 'fix_type' && $unpackInfo[$key]>16 )
                $unpackInfo[$key] = $unpackInfo[$key] % 16;
                
            if(is_nan($unpackInfo[$key])||is_infinite($unpackInfo[$key])){
                $unpackInfo[$key]=0;
            }

            $protocal = array_merge($protocal,$unpackInfo);
            $rememberIndex += $this->_bitSize[$type]*2;
        }
        $protocal['msgid'] = $msgid;
        return $protocal;

    }
    
    public function getD200Protocol($msgid, $payload) {
        if (! isset ( $this->_D200Config [$msgid] ))
            return false;
        
        // $this->typeSize['t'.$msgid] ++;
        
        $rememberIndex = 0;
        $protocal = array ();
        foreach ( $this->_D200Config [$msgid] as $key => $type ) {
            $str = substr ( $payload, $rememberIndex, $this->_bitSize [$type] * 2 );
            
            if (mb_strlen ( $str ) < $this->_bitSize [$type] * 2)
                continue;
            
            if ($msgid == '225') {
                if (! ($unpackInfo = $this->unpack ( $str, $type . '*', $key )))
                    continue;
            } else {
                if (! ($unpackInfo = $this->unpack ( $str, $type, $key )))
                    continue;
            }
            
            if ($msgid == '33' && ($key == 'lat' || $key == 'lon'))
                $unpackInfo [$key] = $unpackInfo [$key] / pow ( 10, 7 );
                
            if($msgid == '24' && $key == 'fix_type' && $unpackInfo[$key]>16 )
                $unpackInfo[$key] = $unpackInfo[$key] % 16;
                
            if(is_nan($unpackInfo[$key])||is_infinite($unpackInfo[$key])){
                $unpackInfo[$key]=0;
            }
            
            $protocal = array_merge ( $protocal, $unpackInfo );
            $rememberIndex += $this->_bitSize [$type] * 2;
        }
        $protocal ['msgid'] = $msgid;
        return $protocal;
    }

    /**
     * @param $str  hex串
     * @param $type 数据类型
     * @param string $key 返回KEY值
     * @return array
     */
    public function unpack($str,$type,$key='*'){
        if(empty($str))
            return false;

        if(mb_strlen($str)%2)
            return false;

        $bin = hex2bin($str);
        $format = $type.$key;
        return unpack($format,$bin);
    }

    /**
     * 获取多个包的解包数据
     * @param array $pachages (引用!)
     */
    public function getPackagesInfo(array &$pachages){
        $this->_runTime = false;
        foreach($pachages as $key =>&$pachage) {
            if (!$this->getPackageData($pachage))
                unset($pachages[$key]);
        }
        $pachages = array_values($pachages);
    }

    /**
     * 获取包数据
     * @param $package array 包数据
     */
    public function getPackageData(array &$package){

        if(!isset($package['message']))
            return false;

        $package['message'] = base64_decode($package['message']);
        $package['message'] = bin2hex($package['message']);


        if(empty($package['message']))
            return false;

        if (strpos ( $package ['message'], '464d' ) === 0) {
            $package ['message'] = explode ( '464d', $package ['message'] );
            
            $data = array ();
            foreach ( $package ['message'] as $key => $message ) {
                
                $prev = substr ( $message, 0, 2 );
                //
                if ($prev != '7e' && $prev != 'ff')
                    continue;
                //
                
                if ($prev == '7e') {
                    
                    if (! ($message = substr ( $message, 2 )))
                        continue;
                    if (empty ( $message ))
                        continue;
                    
                    $len = substr ( $message, 0, 2 );
                    $len = hexdec ( $len );
                    $msgid = substr ( $message, 4, 2 );
                    $msgid = hexdec ( $msgid );
                    $payload = substr ( $message, 6, $len * 2 );
                    
                    if (! isset ( $this->_config [$msgid] ))
                        continue;
                    
                    if (! ($protocal = $this->getProtocol ( $msgid, $payload )))
                        continue;
                    
                    $data [] = $protocal;
                } else {
                    // $data[] = ['upload'=>'1'];
                }
            }
            if (empty ( $data ))
                return false;
        } elseif (strpos ( $package ['message'], '464e' ) === 0) {
            $package ['message'] = explode ( '464e', $package ['message'] );
            
            $data = array ();
            foreach ( $package ['message'] as $key => $message ) {
                
                $prev = substr ( $message, 0, 2 );
                //
                if ($prev != '7e' && $prev != 'ff')
                    continue;
                //
                
                if ($prev == '7e') {
                    
                    if (! ($message = substr ( $message, 2 )))
                        continue;
                    if (empty ( $message ))
                        continue;
                    
                    $len = substr ( $message, 0, 2 );
                    $len = hexdec ( $len );
                    $msgid = substr ( $message, 4, 2 );
                    $msgid = hexdec ( $msgid );
                    $payload = substr ( $message, 6, $len * 2 );
                    
                    if (! isset ( $this->_D200Config [$msgid] ))
                        continue;
                    
                    if (! ($protocal = $this->getD200Protocol ( $msgid, $payload )))
                        continue;
                    
                    $data [] = $protocal;
                } else {
                    // $data[] = ['upload'=>'1'];
                }
            }
            if (empty ( $data ))
                return false;
        }
        
        $createSplitIndex = strrpos ( $package ['created_str'], ':' );
        $timestr = substr ( $package ['created_str'], 0, $createSplitIndex );
        $ms = substr ( $package ['created_str'], $createSplitIndex + 1 );
        //
        list ( $_date, $_time ) = explode ( " ", $timestr );
        $_time = str_replace ( '-', ':', $_time );
        $timestr = $_date . " " . $_time;
        $package = array ();
        $package ['created_str'] = strtotime ( $timestr ) . $ms;
        // $package['msgid'] = $msgid;
        if (! $this->_runTime) {
            $this->_runTime = $package ['created_str'];
            $package ['runtime'] = 11;
        } else
            $package ['runtime'] = $package ['created_str'] - $this->_runTime;
        
        $package ['data'] = $data;
        
        return true;
    }




}